This is a weekend exercise, using materials from a workshop offered by Harvard University’s Institute for Quantitative Social Science (IQSS) program. The original workshop notes can be found on the IQSS website.

Some default set-up

options(width=70)
theme_set(theme_bw())

Grammar of Graphics

The basic idea: independently specify plot building blocks and combine them to create just about any kind of graphical display you want. Building blocks of a graph include:

Take a look at the housing prices dataset.

housing <- read.csv("datasets/landdata-states.csv")
head(housing[,1:5])

Base graphics VS ggplot for histograms

Base graphics histogram example:

hist(housing$Home.Value)

ggplot2 histogram example:

library(ggplot2)
ggplot(housing, aes(x=Home.Value)) + geom_histogram()

Base graphics VS ggplot for scatterplots

Base colored scatterplot example:

plot(Home.Value ~ Date, data=subset(housing, State == "MA"))
points(Home.Value ~ Date, col="red", data=subset(housing, State == "TX"))
# legend(x, y, legend) where x and y are coordinates to position the legend
legend(1975, 400000, c("MA","TX"), title="State", col=c("black", "red"), pch=c(1,1))

ggplot colored scatterplot example:

ggplot(subset(housing, State %in% c("MA", "TX")), aes(x = Date, y = Home.Value)) + geom_point(aes(color=State))

Geometric Objects and Aesthetics

Aesthetic Mapping

In ggplot, aesthetic means something we can see. Examples are:

  • Position (i.e. on the x and y axis)
  • Color (“outside” color)
  • Fill (“inside” color)
  • Shape (of points)
  • Linetype
  • Size

Geometric objects are the actual marks we put on a plot. Examples are:

  • Points (geom_point, for scatter plots, dot plots, etc)
  • Lines (geom_line, for time series, trend lines, etc)
  • Boxplot (geom_boxplot, for, well, boxplots!)

A plot must have at least one geom; there is no upper limit. You can add a geom to a plot using the + operator. We can get a list of available geometric objects using the code below:

help.search("geom_", package = "ggplot2")

Points (scatterplot)

geom_point requires mappings for x and y, all others optional

hp.q12001 <- subset(housing, Date == 2001.25)
ggplot(hp.q12001, aes(x = log(Land.Value), y = Structure.Cost)) + geom_point()

Lines (Prediction lines)

A plot constructed with ggplot can have more than one geom. In that case the mappings established in the ggplot() call are plot defaults that can be added to or overridden. Our plot could use a regression line:

# Create a .SC variable (structure cost), that is just a value of the predict function() using lm
hp.q12001$pred.SC <- predict(lm(Structure.Cost ~ log(Land.Value), data = hp.q12001))
p1 <- ggplot(hp.q12001, aes(x = log(Land.Value), y = Structure.Cost)) + geom_point(aes(color = Home.Value)) + geom_line(aes(y = pred.SC))
p1

Smoothers

Not all geometric objects are simple shapes–the smooth geom includes a line and a ribbon.

p1 + geom_point(aes(color=Home.Value)) + geom_smooth()

Text (label points)

Each geom accepts a particular set of mappings. For example, geom_text() accepts a labels mapping.

p1 + geom_text(aes(label = State), size = 3)

### install.packages("ggrepel")
library(ggrepel)
p1 + geom_point() + geom_text_repel(aes(label = State), size =3)

Aesthetic Mapping vs Assignment

Note that variables are mapped to aesthetics with the aes() function, while fixed aesthetics are set outside the aes() call. This sometimes leads to confusion, as in this example:

p1 + geom_point(aes(size=2), # incorrect! 2 is not a variable
                color = "red") # this is fine -- all points red

Mapping variables to other aesthetics

Other aesthetics are mapped in the same way as x and y in the previous example.

p1 + geom_point(aes(color=Home.Value, shape = region))

Exercise 1

For this exercise, we read in the datasets/EconomistData.csv file.

dat <- read.csv("datasets/EconomistData.csv")
head(dat)

The original sources for these data are Transparency.org and UNDP.

These data consist of Human Development Index and Corruption Perception Index scores for several countries.

  1. Create a scatter plot with CPI on the x axis and HDI on the y axis.
  2. Color the points blue.
  3. Map the color of the the points to Region.
  4. Make the points bigger by setting size to 2
  5. Map the size of the points to HDI.Rank
# Create a scatterplot with CPI on the x-axis and HDI on the y-axis
plot <- ggplot(data = dat, aes(x=CPI, y=HDI))
plot + geom_point()

# Color the points blue
plot + geom_point(col="blue")

# Map the color of the points to Region
plot + geom_point(aes(col = Region))

# Make the points bigger by setting size to 2
plot + geom_point(aes(color = Region), size = 2)

# Map the size of the points to HDI.Rank
plot + geom_point(aes(color = Region, size = HDI.Rank))

Statistical Transformation

Some plot types (such as scatterplots) do not require transformations–each point is plotted at x and y coordinates equal to the original value. Other plots, such as boxplots, histograms, prediction lines etc. require statistical transformations:

Each geom has a default statistic, but these can be changed. For example, the default statistic for geom_bar is stat_bin:

args(geom_histogram)
function (mapping = NULL, data = NULL, stat = "bin", position = "stack", 
    ..., binwidth = NULL, bins = NULL, na.rm = FALSE, show.legend = NA, 
    inherit.aes = TRUE) 
NULL
args(stat_bin)
function (mapping = NULL, data = NULL, geom = "bar", position = "stack", 
    ..., binwidth = NULL, bins = NULL, center = NULL, boundary = NULL, 
    breaks = NULL, closed = c("right", "left"), pad = FALSE, 
    na.rm = FALSE, show.legend = NA, inherit.aes = TRUE) 
NULL

Setting statistical transformation arguments

Arguments to stat_ functions can be passed through geom_ functions. This can be slightly annoying because in order to change it you have to first determine which stat the geom uses, then determine the arguments to that stat.

For example, here is the default histogram of Home.Value:

p2 <- ggplot(housing, aes(x=Home.Value))
p2 + geom_histogram()

Reasonable by default, but we can change it by passing the binwidth argument to the stat_bin function:

p2 + geom_histogram(stat = "bin", binwidth=4000)

Changing the statistical transformation

Sometimes the default statistical transformation is not what you need. This is often the case with pre-summarized data:

housing.sum <- aggregate(x = housing["Home.Value"], by = housing["State"], FUN = mean)
rbind(head(housing.sum), tail(housing.sum))

The following code will throw Error: stat_count() must not be used with a y aesthetic.:

ggplot(housing.sum, aes(x=State, y=Home.Value)) + 
  geom_bar()

This is because we are taking binned (summarized by default) and our summarized data and requesting ggplot to bin and summarize it again. Recall that geom_bar defaults to stat = stat_count. We can override that by explicitly telling geom_bar to use a different statistical transformation function:

ggplot(housing.sum, aes(x=State, y=Home.Value)) + geom_bar(stat="identity")

Exercise 2

  1. Re-create a scatter plot with CPI on the x axis and HDI on the y axis (as you did in the previous exercise).
  2. Overlay a smoothing line on top of the scatter plot using geom_smooth
  3. Overlay a smoothing line on top of the scatter plot using geom_smooth, but use a linear model for the predictions. Hint: see ?stat_smooth.
  4. Overlay a smoothing line on top of the scatter plot using geom_line. Hint: change the statistical transformation.
  5. BONUS: Overlay a smoothing line on top of the scatter plot using the default loess method, but make it less smooth. Hint: see ?loess.
plot2 <- ggplot(data = dat, aes(x=CPI, y=HDI)) + geom_point()
plot2

plot2 + geom_smooth()

# Overlay a smoothing line on top of the scatter plot using `geom_smooth`, but use a linear model for the predictions. Hint: see `?stat_smooth`.
plot2 + geom_smooth(method = "lm")

# Overlay a smoothing line on top of the scatter plot using `geom_line`. Hint: change the statistical transformation.
plot2 + geom_line(aes(y= predict(lm(HDI ~ CPI, data = dat))))

# Bonus: Overlay a smoothing line on top of the scatter plot using the default loess method, but make it less smooth. Hint: see `?loess`.
plot2 + geom_smooth(method = "loess", span = 0.3)

As observed, we can use span to control the “wiggliness” of the default loess smoother. The span is the fraction of points used to fit each local regression. We use 0.3 or a smaller number than that to achieve a wigglier curver.

Scales

Scales: Controlling aesthetic mapping

Aesthetic mapping (i.e., with aes()) only says that a variable should be mapped to an aesthetic. It doesn’t say how that should happen. For example, when mapping a variable to shape with aes(shape = x) you don’t say what shapes should be used. Similarly, aes(color = z) doesn’t say what colors should be used. Describing what colors/shapes/sizes etc. to use is done by modifying the corresponding scale. In ggplot2 scales include:

  • Position
  • Color and fill
  • Size
  • Shape
  • Line type

Scales are modified with a series of functions using a scale_<aesthetic>_<type> naming scheme. Try typing scale_<tab> to see a list of scale modification functions.

Common scale arguments

  • name
    • the first argument gives the axis or legend title
  • limits
    • the minimum and maximum of the scale
  • breaks
    • the points along the scale where labels should appear
  • labels
    • the labels that appear at each break

Specific scale functions may have additional arguments; for example, the scale_color_continuous function has arguments low and high for setting the colors at the low and high end of the scale.

Scale modification examples

Start by constructing a dotplot showing the distribution of home values by Date and State.

p3 <- ggplot(housing, aes(x=State, y=Home.Price.Index)) + theme(legend.position = "top", axis.text = element_text(size=6))
p4 <- p3 + geom_point(aes(color=Date), alpha=0.5, size=1.5, position = position_jitter(width=0.25, height=0))
p4

Now modify the breaks for the x axis and color scales

p4 + scale_x_discrete(name="State Abbreviation") + scale_color_continuous(name="", breaks = c(1976, 1994, 2013), labels = c("'76", "'94", "'13"))

Next change the low and high values to blue and red:

p4 + scale_x_discrete("State Abbreviation") + scale_color_continuous(name = "", breaks = c(1976, 1994, 2013), labels = c("'76", "'94", "'13"), low="blue", high="red")

library(scales)
p4 +
  scale_color_continuous(name="",
                         breaks = c(1976, 1994, 2013),
                         labels = c("'76", "'94", "'13"),
                         low = muted("blue"), high = muted("red"))

Use difference color scales

ggplot2 has a wide variety of color scales; here is an example using scale_color_gradient2 to interpolate between three different colors.

p4 + scale_color_gradient2(name = "", breaks = c(1976, 1994, 2013), labels = c("'76", "'94", "'13"), low = muted("blue"), high = muted("red"), mid = "gray60", midpoint = 1994)

Exercise 3

  1. Create a scatter plot with CPI on the x axis and HDI on the y axis. Color the points to indicate region.
  2. Modify the x, y, and color scales so that they have more easily-understood names (e.g., spell out “Human development Index” instead of “HDI”).
  3. Modify the color scale to use specific values of your choosing. Hint: see ?scale_color_manual.
# Create a scatterplot with CPI on the x axis and HDI on the y axis. Color the points to indicate region
plot3 <- ggplot(data = dat, aes(x=CPI, y=HDI)) + geom_point(aes(color=Region))
plot3

# Modify the x, y, and color scales so that they have more easily-understood names (e.g., spell out "Human development Index" instead of "HDI").
plot3 + scale_x_continuous(name="Corruption Perception Index") + scale_y_continuous(name="Human Development Index") + scale_color_discrete(labels=c("Americas", "Asia Pacific", "Eastern Europe and Central Asia", "Western Europe", "Middle East and North Africa", "Sub-Saharan Africa"))

# Modify the color scale to use specific values of your choosing. Hint: see `?scale_color_manual`
plot3 + scale_x_continuous(name="Corruption Perception Index") + scale_y_continuous(name="Human Development Index") + scale_color_manual(name = "Region", values = c("dodgerblue4", "darkolivegreen4", "darkorchid3", "goldenrod1", "chocolate2", "violetred4"), labels=c("Americas", "Asia Pacific", "Eastern Europe and Central Asia", "Western Europe", "Middle East and North Africa", "Sub-Saharan Africa"))

# Bonus: Add geom_text
plot3 + scale_x_continuous(name="Corruption Perception Index") + scale_y_continuous(name="Human Development Index") + scale_color_manual(name = "Region", values = c("dodgerblue4", "darkolivegreen4", "darkorchid3", "goldenrod1", "chocolate2", "violetred4"), labels=c("Americas", "Asia Pacific", "Eastern Europe and Central Asia", "Western Europe", "Middle East and North Africa", "Sub-Saharan Africa")) + geom_text_repel(aes(label=Country), data = dat[dat$Country %in% c("Singapore", "Malaysia", "Indonesia", "Vietnam","Philippines", "Thailand"),], size=2)

Faceting

Faceting concepts

Faceting is ggplot2 parlance for small multiples. The idea is to create separate graphs for subsets of data. ggplot2 offers two functions for creating small multiples:

  • facet_wrap(): define subsets as the levels of a single grouping variable
  • facet_grid(): define subsets as the crossing of two grouping variabls

This facilitates comparison among plots, not just of geoms within a plot.

What is the trend in housing prices in each state?

# Start by using a technique we already know: map State to color
p5 <- ggplot(housing, aes(x = Date, y = Home.Value))
p5 + geom_line(aes(color = State))

There are two problems here–there are too many states to distinguish each one by color, and the lines obscure one another.

Faceting to the rescue

We can remedy the deficiencies of the previous plot by faceting by state rather than mapping state to color.

p5 <- p5 + geom_line() + facet_wrap(~State, ncol=10)
p5

There is also a facet_grid() function for faceting in two dimensions.

Themes

The ggplot2 theme system handles non-data plot elements such as:

Built-in themes include:

p5 + theme_linedraw()

p5 + theme_light()

Overriding theme defaults

Specific theme elements can be overridden using theme():

p5 + theme_minimal() + theme(text = element_text(color="purple"))

All theme options are documented in ?theme.

Creating and saving new themes

You can create new themes, as in the following example:

theme_new <- theme_bw() +
  theme(plot.background = element_rect(size=1, color="maroon", fill="black"), 
        text=element_text(size=11, color="goldenrod2"),
        axis.text.y = element_text(color="orange"),
        axis.text.x = element_text(color="orange"),
        panel.background = element_rect(fill = "goldenrod3"), 
        strip.background = element_rect(fill="maroon"))
p5 + theme_new

Challenge: Recreate an Economist graph

Building off of the graphics you created in the previous exercises, put the finishing touches to make it as close as possible to the original economist graph

# Import the fonts installed on our system. Only do this once. 
# library(extrafont)
# font_import()
# Register the font with the PDF output device
# loadfonts()
# Rename / Re-order the factor levels before the plot
dat$Region <- factor(dat$Region, levels = c("EU W. Europe", "Americas", "Asia Pacific", "East EU Cemt Asia", "MENA", "SSA"))
# List of countries with labels
countryList <- c("New Zealand", "Singapore", "Norway", "Japan","Germany", "Britain", "Barbados", "US", "France", "Spain", "Botswana", "Cape Verde", "Bhutan", "Italy", "Greece", "Brazil", "Argentina", "China", "South Africa", "Rwanda", "India", "Congo", "Russia", "Venezuela", "Iraq", "Myanmar", "Sudan", "Afghanistan")
# Develop our new theme
econ_theme <- theme_bw() + 
  theme(aspect.ratio = 3/7,
        legend.text = element_text(size = 7, family="Arial Narrow"),
        legend.position = c(0,1),
        legend.justification = "left",
        legend.direction = "horizontal",
        legend.spacing = unit(2, "lines"),
        plot.margin = unit(c(0.5, 1, 0.5, 0.5), units="line"),
        axis.title.y=element_text(face="italic"),
        axis.title.x=element_text(face="italic"),
        plot.title = element_text(face = "bold", size=13),
        panel.border = element_blank(),
        panel.grid.major.x = element_blank(),
        panel.grid.minor.x = element_blank(),
        panel.grid.major.y = element_line(size=.1, color="grey66"),
        panel.grid.minor.y = element_blank())
econ <- ggplot(data = dat, aes(x=CPI, y=HDI)) + 
  geom_point(aes(color=Region), size=1.5, fill=4, shape=1, stroke = 1.2) + 
  geom_smooth(method = "lm", formula = y ~ log(x), se=FALSE, color="red", linetype=1, weight=2) + 
  geom_text_repel(aes(label=Country), data = dat[dat$Country %in% countryList,], size=2.2) +
  scale_x_continuous(name="Corruption Perceptions Index, 2011(10=least corrupt)", breaks=seq(1,10,by=1), limits=c(1,10)) +
  scale_y_continuous(name="Human Development Index, 2011(1=best)", breaks=seq(0, 1.0, by=0.1), limits= c(0.2,1.0)) +
  scale_color_manual(name = "", values = c("#2d6074", "#29adde", "#bbe9fb", "#188c81", "#f2523a", "#7c2510"), labels=c("EU W. Europe"="OECD", "Americas" = "Americas", "Asia Pacific"="Asia & Oceania", "East EU Cemt Asia"="Central & Eastern Europe", "MENA"="Middle East & north Africa", "SSA"="Sub-Saharan Africa")) +
  labs(title="Corruption and human development") +
  guides(color=guide_legend(nrow=1)) +
  econ_theme 
econ

LS0tCnRpdGxlOiAiSW50cm9kdWN0aW9uIHRvIFIgR3JhcGhpY3Mgd2l0aCBnZ3Bsb3QyIChJUVNTKSIKb3V0cHV0OiBodG1sX25vdGVib29rCmF1dGhvcjogIlNhbXVlbCBDaGFuIgpkYXRlOiAiMTkvMDIvMjAxNyIKLS0tCgpUaGlzIGlzIGEgd2Vla2VuZCBleGVyY2lzZSwgdXNpbmcgbWF0ZXJpYWxzIGZyb20gYSB3b3Jrc2hvcCBvZmZlcmVkIGJ5IEhhcnZhcmQgVW5pdmVyc2l0eSdzIEluc3RpdHV0ZSBmb3IgUXVhbnRpdGF0aXZlIFNvY2lhbCBTY2llbmNlIChJUVNTKSBwcm9ncmFtLiBUaGUgb3JpZ2luYWwgd29ya3Nob3Agbm90ZXMgY2FuIGJlIGZvdW5kIG9uIHRoZSBbSVFTUyB3ZWJzaXRlXShodHRwOi8vdHV0b3JpYWxzLmlxLmhhcnZhcmQuZWR1L1IvUmdyYXBoaWNzL1JncmFwaGljcy5odG1sKS4KClNvbWUgZGVmYXVsdCBzZXQtdXAKYGBge3J9Cm9wdGlvbnMod2lkdGg9NzApCnRoZW1lX3NldCh0aGVtZV9idygpKQpgYGAKCgojIEdyYW1tYXIgb2YgR3JhcGhpY3MKVGhlIGJhc2ljIGlkZWE6IGluZGVwZW5kZW50bHkgc3BlY2lmeSBwbG90IGJ1aWxkaW5nIGJsb2NrcyBhbmQgY29tYmluZSB0aGVtIHRvIGNyZWF0ZSBqdXN0IGFib3V0IGFueSBraW5kIG9mIGdyYXBoaWNhbCBkaXNwbGF5IHlvdSB3YW50LiBCdWlsZGluZyBibG9ja3Mgb2YgYSBncmFwaCBpbmNsdWRlOgoKLSBEYXRhCi0gQWVzdGhldGljIG1hcHBpbmcKLSBHZW9tZXRyaWMgb2JqZWN0Ci0gU3RhdGlzdGljYWwgdHJhbnNmb3JtYXRpb25zCi0gU2NhbGVzCi0gQ29vcmRpbmF0ZSBzeXN0ZW0KLSBQb3NpdGlvbiBhZGp1c3RtZW50cwotIEZhY2V0aW5nCgpUYWtlIGEgbG9vayBhdCB0aGUgaG91c2luZyBwcmljZXMgZGF0YXNldC4gCmBgYHtyfQpob3VzaW5nIDwtIHJlYWQuY3N2KCJkYXRhc2V0cy9sYW5kZGF0YS1zdGF0ZXMuY3N2IikKaGVhZChob3VzaW5nWywxOjVdKQpgYGAKCiMjIyBgQmFzZWAgZ3JhcGhpY3MgVlMgYGdncGxvdGAgZm9yIGhpc3RvZ3JhbXMKCkJhc2UgZ3JhcGhpY3MgaGlzdG9ncmFtIGV4YW1wbGU6CmBgYHtyfQpoaXN0KGhvdXNpbmckSG9tZS5WYWx1ZSkKYGBgCgpgZ2dwbG90MmAgaGlzdG9ncmFtIGV4YW1wbGU6CmBgYHtyfQpsaWJyYXJ5KGdncGxvdDIpCmdncGxvdChob3VzaW5nLCBhZXMoeD1Ib21lLlZhbHVlKSkgKyBnZW9tX2hpc3RvZ3JhbSgpCmBgYAoKCiMjIyBgQmFzZWAgZ3JhcGhpY3MgVlMgYGdncGxvdGAgZm9yIHNjYXR0ZXJwbG90cwoKQmFzZSBjb2xvcmVkIHNjYXR0ZXJwbG90IGV4YW1wbGU6CmBgYHtyfQpwbG90KEhvbWUuVmFsdWUgfiBEYXRlLCBkYXRhPXN1YnNldChob3VzaW5nLCBTdGF0ZSA9PSAiTUEiKSkKcG9pbnRzKEhvbWUuVmFsdWUgfiBEYXRlLCBjb2w9InJlZCIsIGRhdGE9c3Vic2V0KGhvdXNpbmcsIFN0YXRlID09ICJUWCIpKQojIGxlZ2VuZCh4LCB5LCBsZWdlbmQpIHdoZXJlIHggYW5kIHkgYXJlIGNvb3JkaW5hdGVzIHRvIHBvc2l0aW9uIHRoZSBsZWdlbmQKbGVnZW5kKDE5NzUsIDQwMDAwMCwgYygiTUEiLCJUWCIpLCB0aXRsZT0iU3RhdGUiLCBjb2w9YygiYmxhY2siLCAicmVkIiksIHBjaD1jKDEsMSkpCmBgYAoKYGdncGxvdGAgY29sb3JlZCBzY2F0dGVycGxvdCBleGFtcGxlOgpgYGB7cn0KZ2dwbG90KHN1YnNldChob3VzaW5nLCBTdGF0ZSAlaW4lIGMoIk1BIiwgIlRYIikpLCBhZXMoeCA9IERhdGUsIHkgPSBIb21lLlZhbHVlKSkgKyBnZW9tX3BvaW50KGFlcyhjb2xvcj1TdGF0ZSkpCmBgYAoKIyBHZW9tZXRyaWMgT2JqZWN0cyBhbmQgQWVzdGhldGljcwojIyMgQWVzdGhldGljIE1hcHBpbmcKCkluIGdncGxvdCwgYWVzdGhldGljIG1lYW5zIHNvbWV0aGluZyB3ZSBjYW4gc2VlLiBFeGFtcGxlcyBhcmU6CgotIFBvc2l0aW9uIChpLmUuIG9uIHRoZSB4IGFuZCB5IGF4aXMpCi0gQ29sb3IgKCJvdXRzaWRlIiBjb2xvcikKLSBGaWxsICgiaW5zaWRlIiBjb2xvcikKLSBTaGFwZSAob2YgcG9pbnRzKQotIExpbmV0eXBlCi0gU2l6ZQoKR2VvbWV0cmljIG9iamVjdHMgYXJlIHRoZSBhY3R1YWwgbWFya3Mgd2UgcHV0IG9uIGEgcGxvdC4gRXhhbXBsZXMgYXJlOgoKLSBQb2ludHMgKGdlb21fcG9pbnQsIGZvciBzY2F0dGVyIHBsb3RzLCBkb3QgcGxvdHMsIGV0YykKLSBMaW5lcyAoZ2VvbV9saW5lLCBmb3IgdGltZSBzZXJpZXMsIHRyZW5kIGxpbmVzLCBldGMpCi0gQm94cGxvdCAoZ2VvbV9ib3hwbG90LCBmb3IsIHdlbGwsIGJveHBsb3RzISkKCkEgcGxvdCBtdXN0IGhhdmUgYXQgbGVhc3Qgb25lIGdlb207IHRoZXJlIGlzIG5vIHVwcGVyIGxpbWl0LiBZb3UgY2FuIGFkZCBhIGdlb20gdG8gYSBwbG90IHVzaW5nIHRoZSArIG9wZXJhdG9yLiBXZSBjYW4gZ2V0IGEgbGlzdCBvZiBhdmFpbGFibGUgZ2VvbWV0cmljIG9iamVjdHMgdXNpbmcgdGhlIGNvZGUgYmVsb3c6CmBgYHtyfQpoZWxwLnNlYXJjaCgiZ2VvbV8iLCBwYWNrYWdlID0gImdncGxvdDIiKQpgYGAKCiMjIyBQb2ludHMgKHNjYXR0ZXJwbG90KQpgZ2VvbV9wb2ludGAgcmVxdWlyZXMgbWFwcGluZ3MgZm9yIHggYW5kIHksIGFsbCBvdGhlcnMgb3B0aW9uYWwKYGBge3J9CmhwLnExMjAwMSA8LSBzdWJzZXQoaG91c2luZywgRGF0ZSA9PSAyMDAxLjI1KQpnZ3Bsb3QoaHAucTEyMDAxLCBhZXMoeCA9IGxvZyhMYW5kLlZhbHVlKSwgeSA9IFN0cnVjdHVyZS5Db3N0KSkgKyBnZW9tX3BvaW50KCkKYGBgCgojIyMgTGluZXMgKFByZWRpY3Rpb24gbGluZXMpCkEgcGxvdCBjb25zdHJ1Y3RlZCB3aXRoIGdncGxvdCBjYW4gaGF2ZSBtb3JlIHRoYW4gb25lIGdlb20uIEluIHRoYXQgY2FzZSB0aGUgbWFwcGluZ3MgZXN0YWJsaXNoZWQgaW4gdGhlIGdncGxvdCgpIGNhbGwgYXJlIHBsb3QgZGVmYXVsdHMgdGhhdCBjYW4gYmUgYWRkZWQgdG8gb3Igb3ZlcnJpZGRlbi4gT3VyIHBsb3QgY291bGQgdXNlIGEgcmVncmVzc2lvbiBsaW5lOgpgYGB7cn0KIyBDcmVhdGUgYSAuU0MgdmFyaWFibGUgKHN0cnVjdHVyZSBjb3N0KSwgdGhhdCBpcyBqdXN0IGEgdmFsdWUgb2YgdGhlIHByZWRpY3QgZnVuY3Rpb24oKSB1c2luZyBsbQpocC5xMTIwMDEkcHJlZC5TQyA8LSBwcmVkaWN0KGxtKFN0cnVjdHVyZS5Db3N0IH4gbG9nKExhbmQuVmFsdWUpLCBkYXRhID0gaHAucTEyMDAxKSkKCnAxIDwtIGdncGxvdChocC5xMTIwMDEsIGFlcyh4ID0gbG9nKExhbmQuVmFsdWUpLCB5ID0gU3RydWN0dXJlLkNvc3QpKSArIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gSG9tZS5WYWx1ZSkpICsgZ2VvbV9saW5lKGFlcyh5ID0gcHJlZC5TQykpCnAxCmBgYAoKIyMjIFNtb290aGVycwpOb3QgYWxsIGdlb21ldHJpYyBvYmplY3RzIGFyZSBzaW1wbGUgc2hhcGVz4oCTdGhlIHNtb290aCBnZW9tIGluY2x1ZGVzIGEgbGluZSBhbmQgYSByaWJib24uCmBgYHtyfQpwMSArIGdlb21fcG9pbnQoYWVzKGNvbG9yPUhvbWUuVmFsdWUpKSArIGdlb21fc21vb3RoKCkKYGBgCgojIyMgVGV4dCAobGFiZWwgcG9pbnRzKQpFYWNoIGBnZW9tYCBhY2NlcHRzIGEgcGFydGljdWxhciBzZXQgb2YgbWFwcGluZ3MuIEZvciBleGFtcGxlLCBnZW9tX3RleHQoKSBhY2NlcHRzIGEgbGFiZWxzIG1hcHBpbmcuCmBgYHtyfQpwMSArIGdlb21fdGV4dChhZXMobGFiZWwgPSBTdGF0ZSksIHNpemUgPSAzKQpgYGAKCmBgYHtyfQojIyMgaW5zdGFsbC5wYWNrYWdlcygiZ2dyZXBlbCIpCmxpYnJhcnkoZ2dyZXBlbCkKcDEgKyBnZW9tX3BvaW50KCkgKyBnZW9tX3RleHRfcmVwZWwoYWVzKGxhYmVsID0gU3RhdGUpLCBzaXplID0zKQpgYGAKCiMjIyBBZXN0aGV0aWMgTWFwcGluZyB2cyBBc3NpZ25tZW50Ck5vdGUgdGhhdCB2YXJpYWJsZXMgYXJlIG1hcHBlZCB0byBhZXN0aGV0aWNzIHdpdGggdGhlIGFlcygpIGZ1bmN0aW9uLCB3aGlsZSBmaXhlZCBhZXN0aGV0aWNzIGFyZSBzZXQgb3V0c2lkZSB0aGUgYWVzKCkgY2FsbC4gVGhpcyBzb21ldGltZXMgbGVhZHMgdG8gY29uZnVzaW9uLCBhcyBpbiB0aGlzIGV4YW1wbGU6CmBgYHtyfQpwMSArIGdlb21fcG9pbnQoYWVzKHNpemU9MiksICMgaW5jb3JyZWN0ISAyIGlzIG5vdCBhIHZhcmlhYmxlCiAgICAgICAgICAgICAgICBjb2xvciA9ICJyZWQiKSAjIHRoaXMgaXMgZmluZSAtLSBhbGwgcG9pbnRzIHJlZApgYGAKCiMjIyBNYXBwaW5nIHZhcmlhYmxlcyB0byBvdGhlciBhZXN0aGV0aWNzCk90aGVyIGFlc3RoZXRpY3MgYXJlIG1hcHBlZCBpbiB0aGUgc2FtZSB3YXkgYXMgeCBhbmQgeSBpbiB0aGUgcHJldmlvdXMgZXhhbXBsZS4KYGBge3J9CnAxICsgZ2VvbV9wb2ludChhZXMoY29sb3I9SG9tZS5WYWx1ZSwgc2hhcGUgPSByZWdpb24pKQpgYGAKCiMjIyBFeGVyY2lzZSAxCkZvciB0aGlzIGV4ZXJjaXNlLCB3ZSByZWFkIGluIHRoZSBgZGF0YXNldHMvRWNvbm9taXN0RGF0YS5jc3ZgIGZpbGUuCmBgYHtyfQpkYXQgPC0gcmVhZC5jc3YoImRhdGFzZXRzL0Vjb25vbWlzdERhdGEuY3N2IikKaGVhZChkYXQpCmBgYAoKVGhlIG9yaWdpbmFsIHNvdXJjZXMgZm9yIHRoZXNlIGRhdGEgYXJlIFtUcmFuc3BhcmVuY3kub3JnXShodHRwOi8vd3d3LnRyYW5zcGFyZW5jeS5vcmcvY29udGVudC9kb3dubG9hZC82NDQ3Ni8xMDMxNDI4KSBhbmQgW1VORFBdKGh0dHA6Ly9oZHJzdGF0cy51bmRwLm9yZy9lbi9pbmRpY2F0b3JzL2Rpc3BsYXlfY2ZfeGxzX2luZGljYXRvci5jZm0/aW5kaWNhdG9yX2lkPTEwMzEwNiZsYW5nPWVuKS4gCgpUaGVzZSBkYXRhIGNvbnNpc3Qgb2YgSHVtYW4gRGV2ZWxvcG1lbnQgSW5kZXggYW5kIENvcnJ1cHRpb24gUGVyY2VwdGlvbiBJbmRleCBzY29yZXMgZm9yIHNldmVyYWwgY291bnRyaWVzLgoKMS4gQ3JlYXRlIGEgc2NhdHRlciBwbG90IHdpdGggQ1BJIG9uIHRoZSB4IGF4aXMgYW5kIEhESSBvbiB0aGUgeSBheGlzLgoyLiBDb2xvciB0aGUgcG9pbnRzIGJsdWUuCjMuIE1hcCB0aGUgY29sb3Igb2YgdGhlIHRoZSBwb2ludHMgdG8gUmVnaW9uLgo0LiBNYWtlIHRoZSBwb2ludHMgYmlnZ2VyIGJ5IHNldHRpbmcgc2l6ZSB0byAyCjUuIE1hcCB0aGUgc2l6ZSBvZiB0aGUgcG9pbnRzIHRvIEhESS5SYW5rCgpgYGB7cn0KIyBDcmVhdGUgYSBzY2F0dGVycGxvdCB3aXRoIENQSSBvbiB0aGUgeC1heGlzIGFuZCBIREkgb24gdGhlIHktYXhpcwpwbG90IDwtIGdncGxvdChkYXRhID0gZGF0LCBhZXMoeD1DUEksIHk9SERJKSkKcGxvdCArIGdlb21fcG9pbnQoKQpgYGAKCmBgYHtyfQojIENvbG9yIHRoZSBwb2ludHMgYmx1ZQpwbG90ICsgZ2VvbV9wb2ludChjb2w9ImJsdWUiKQpgYGAKCmBgYHtyfQojIE1hcCB0aGUgY29sb3Igb2YgdGhlIHBvaW50cyB0byBSZWdpb24KcGxvdCArIGdlb21fcG9pbnQoYWVzKGNvbCA9IFJlZ2lvbikpCmBgYAoKYGBge3J9CiMgTWFrZSB0aGUgcG9pbnRzIGJpZ2dlciBieSBzZXR0aW5nIHNpemUgdG8gMgpwbG90ICsgZ2VvbV9wb2ludChhZXMoY29sb3IgPSBSZWdpb24pLCBzaXplID0gMikKYGBgCgpgYGB7cn0KIyBNYXAgdGhlIHNpemUgb2YgdGhlIHBvaW50cyB0byBIREkuUmFuawpwbG90ICsgZ2VvbV9wb2ludChhZXMoY29sb3IgPSBSZWdpb24sIHNpemUgPSBIREkuUmFuaykpCmBgYAoKIyBTdGF0aXN0aWNhbCBUcmFuc2Zvcm1hdGlvbgpTb21lIHBsb3QgdHlwZXMgKHN1Y2ggYXMgc2NhdHRlcnBsb3RzKSBkbyBub3QgcmVxdWlyZSB0cmFuc2Zvcm1hdGlvbnPigJNlYWNoIHBvaW50IGlzIHBsb3R0ZWQgYXQgeCBhbmQgeSBjb29yZGluYXRlcyBlcXVhbCB0byB0aGUgb3JpZ2luYWwgdmFsdWUuIE90aGVyIHBsb3RzLCBzdWNoIGFzIGJveHBsb3RzLCBoaXN0b2dyYW1zLCBwcmVkaWN0aW9uIGxpbmVzIGV0Yy4gcmVxdWlyZSBzdGF0aXN0aWNhbCB0cmFuc2Zvcm1hdGlvbnM6CgotIEZvciBhIGJveHBsb3QgdGhlIHkgdmFsdWVzIG11c3QgYmUgdHJhbnNmb3JtZWQgdG8gdGhlIG1lZGlhbiBhbmQgMS41KElRUikKLSBGb3IgYSBzbW9vdGhlciBwbG90IHRoZSB5IHZhbHVlcyBtdXN0IGJlIHRyYW5zZm9ybWVkIGludG8gcHJlZGljdGVkIHZhbHVlcwoKRWFjaCBgZ2VvbWAgaGFzIGEgZGVmYXVsdCBzdGF0aXN0aWMsIGJ1dCB0aGVzZSBjYW4gYmUgY2hhbmdlZC4gRm9yIGV4YW1wbGUsIHRoZSBkZWZhdWx0IHN0YXRpc3RpYyBmb3IgZ2VvbV9iYXIgaXMgYHN0YXRfYmluYDoKYGBge3J9CmFyZ3MoZ2VvbV9oaXN0b2dyYW0pCmFyZ3Moc3RhdF9iaW4pCmBgYAoKIyMjIFNldHRpbmcgc3RhdGlzdGljYWwgdHJhbnNmb3JtYXRpb24gYXJndW1lbnRzCkFyZ3VtZW50cyB0byBzdGF0XyBmdW5jdGlvbnMgY2FuIGJlIHBhc3NlZCB0aHJvdWdoIGdlb21fIGZ1bmN0aW9ucy4gVGhpcyBjYW4gYmUgc2xpZ2h0bHkgYW5ub3lpbmcgYmVjYXVzZSBpbiBvcmRlciB0byBjaGFuZ2UgaXQgeW91IGhhdmUgdG8gZmlyc3QgZGV0ZXJtaW5lIHdoaWNoIHN0YXQgdGhlIGdlb20gdXNlcywgdGhlbiBkZXRlcm1pbmUgdGhlIGFyZ3VtZW50cyB0byB0aGF0IHN0YXQuCgpGb3IgZXhhbXBsZSwgaGVyZSBpcyB0aGUgZGVmYXVsdCBoaXN0b2dyYW0gb2YgSG9tZS5WYWx1ZToKYGBge3J9CnAyIDwtIGdncGxvdChob3VzaW5nLCBhZXMoeD1Ib21lLlZhbHVlKSkKcDIgKyBnZW9tX2hpc3RvZ3JhbSgpCmBgYAoKUmVhc29uYWJsZSBieSBkZWZhdWx0LCBidXQgd2UgY2FuIGNoYW5nZSBpdCBieSBwYXNzaW5nIHRoZSBgYmlud2lkdGhgIGFyZ3VtZW50IHRvIHRoZSBgc3RhdF9iaW5gIGZ1bmN0aW9uOgoKYGBge3J9CnAyICsgZ2VvbV9oaXN0b2dyYW0oc3RhdCA9ICJiaW4iLCBiaW53aWR0aD00MDAwKQpgYGAKCiMjIyBDaGFuZ2luZyB0aGUgc3RhdGlzdGljYWwgdHJhbnNmb3JtYXRpb24KU29tZXRpbWVzIHRoZSBkZWZhdWx0IHN0YXRpc3RpY2FsIHRyYW5zZm9ybWF0aW9uIGlzIG5vdCB3aGF0IHlvdSBuZWVkLiBUaGlzIGlzIG9mdGVuIHRoZSBjYXNlIHdpdGggcHJlLXN1bW1hcml6ZWQgZGF0YToKCmBgYHtyfQpob3VzaW5nLnN1bSA8LSBhZ2dyZWdhdGUoeCA9IGhvdXNpbmdbIkhvbWUuVmFsdWUiXSwgYnkgPSBob3VzaW5nWyJTdGF0ZSJdLCBGVU4gPSBtZWFuKQpyYmluZChoZWFkKGhvdXNpbmcuc3VtKSwgdGFpbChob3VzaW5nLnN1bSkpCmBgYAoKVGhlIGZvbGxvd2luZyBjb2RlIHdpbGwgdGhyb3cgYEVycm9yOiBzdGF0X2NvdW50KCkgbXVzdCBub3QgYmUgdXNlZCB3aXRoIGEgeSBhZXN0aGV0aWMuYDoKYGBge3J9CmdncGxvdChob3VzaW5nLnN1bSwgYWVzKHg9U3RhdGUsIHk9SG9tZS5WYWx1ZSkpICsgCiAgZ2VvbV9iYXIoKQpgYGAKClRoaXMgaXMgYmVjYXVzZSB3ZSBhcmUgdGFraW5nIGJpbm5lZCAoc3VtbWFyaXplZCBieSBkZWZhdWx0KSBhbmQgb3VyIHN1bW1hcml6ZWQgZGF0YSBhbmQgcmVxdWVzdGluZyBnZ3Bsb3QgdG8gYmluIGFuZCBzdW1tYXJpemUgaXQgYWdhaW4uIFJlY2FsbCB0aGF0IGdlb21fYmFyIGRlZmF1bHRzIHRvIHN0YXQgPSBzdGF0X2NvdW50LiBXZSBjYW4gb3ZlcnJpZGUgdGhhdCBieSBleHBsaWNpdGx5IHRlbGxpbmcgZ2VvbV9iYXIgdG8gdXNlIGEgZGlmZmVyZW50IHN0YXRpc3RpY2FsIHRyYW5zZm9ybWF0aW9uIGZ1bmN0aW9uOiAKYGBge3J9CmdncGxvdChob3VzaW5nLnN1bSwgYWVzKHg9U3RhdGUsIHk9SG9tZS5WYWx1ZSkpICsgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiKQpgYGAKCiMjIyBFeGVyY2lzZSAyCgoxLiBSZS1jcmVhdGUgYSBzY2F0dGVyIHBsb3Qgd2l0aCBDUEkgb24gdGhlIHggYXhpcyBhbmQgSERJIG9uIHRoZSB5IGF4aXMgKGFzIHlvdSBkaWQgaW4gdGhlIHByZXZpb3VzIGV4ZXJjaXNlKS4KMi4gT3ZlcmxheSBhIHNtb290aGluZyBsaW5lIG9uIHRvcCBvZiB0aGUgc2NhdHRlciBwbG90IHVzaW5nIGBnZW9tX3Ntb290aGAKMy4gT3ZlcmxheSBhIHNtb290aGluZyBsaW5lIG9uIHRvcCBvZiB0aGUgc2NhdHRlciBwbG90IHVzaW5nIGBnZW9tX3Ntb290aGAsIGJ1dCB1c2UgYSBsaW5lYXIgbW9kZWwgZm9yIHRoZSBwcmVkaWN0aW9ucy4gSGludDogc2VlIGA/c3RhdF9zbW9vdGhgLgo0LiBPdmVybGF5IGEgc21vb3RoaW5nIGxpbmUgb24gdG9wIG9mIHRoZSBzY2F0dGVyIHBsb3QgdXNpbmcgYGdlb21fbGluZWAuIEhpbnQ6IGNoYW5nZSB0aGUgc3RhdGlzdGljYWwgdHJhbnNmb3JtYXRpb24uCjUuIEJPTlVTOiBPdmVybGF5IGEgc21vb3RoaW5nIGxpbmUgb24gdG9wIG9mIHRoZSBzY2F0dGVyIHBsb3QgdXNpbmcgdGhlIGRlZmF1bHQgbG9lc3MgbWV0aG9kLCBidXQgbWFrZSBpdCBsZXNzIHNtb290aC4gSGludDogc2VlIGA/bG9lc3NgLgoKYGBge3J9CnBsb3QyIDwtIGdncGxvdChkYXRhID0gZGF0LCBhZXMoeD1DUEksIHk9SERJKSkgKyBnZW9tX3BvaW50KCkKcGxvdDIKYGBgCgpgYGB7cn0KIyBPdmVybGF5IGEgc21vb3RoaW5nIGxpbmUgb24gdG9wIG9mIHRoZSBzY2F0dGVyIHBsb3QgdXNpbmcgYGdlb21fc21vb3RoYApwbG90MiArIGdlb21fc21vb3RoKCkKYGBgCgpgYGB7cn0KIyBPdmVybGF5IGEgc21vb3RoaW5nIGxpbmUgb24gdG9wIG9mIHRoZSBzY2F0dGVyIHBsb3QgdXNpbmcgYGdlb21fc21vb3RoYCwgYnV0IHVzZSBhIGxpbmVhciBtb2RlbCBmb3IgdGhlIHByZWRpY3Rpb25zLiBIaW50OiBzZWUgYD9zdGF0X3Ntb290aGAuCnBsb3QyICsgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIikKYGBgCgpgYGB7cn0KIyBPdmVybGF5IGEgc21vb3RoaW5nIGxpbmUgb24gdG9wIG9mIHRoZSBzY2F0dGVyIHBsb3QgdXNpbmcgYGdlb21fbGluZWAuIEhpbnQ6IGNoYW5nZSB0aGUgc3RhdGlzdGljYWwgdHJhbnNmb3JtYXRpb24uCnBsb3QyICsgZ2VvbV9saW5lKGFlcyh5PSBwcmVkaWN0KGxtKEhESSB+IENQSSwgZGF0YSA9IGRhdCkpKSkKCmBgYAoKYGBge3J9CiMgQm9udXM6IE92ZXJsYXkgYSBzbW9vdGhpbmcgbGluZSBvbiB0b3Agb2YgdGhlIHNjYXR0ZXIgcGxvdCB1c2luZyB0aGUgZGVmYXVsdCBsb2VzcyBtZXRob2QsIGJ1dCBtYWtlIGl0IGxlc3Mgc21vb3RoLiBIaW50OiBzZWUgYD9sb2Vzc2AuCgpwbG90MiArIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsb2VzcyIsIHNwYW4gPSAwLjMpCmBgYAoKQXMgb2JzZXJ2ZWQsIHdlIGNhbiB1c2UgYHNwYW5gIHRvIGNvbnRyb2wgdGhlICJ3aWdnbGluZXNzIiBvZiB0aGUgZGVmYXVsdCBsb2VzcyBzbW9vdGhlci4gVGhlIHNwYW4gaXMgdGhlIGZyYWN0aW9uIG9mIHBvaW50cyB1c2VkIHRvIGZpdCBlYWNoIGxvY2FsIHJlZ3Jlc3Npb24uCldlIHVzZSAwLjMgb3IgYSBzbWFsbGVyIG51bWJlciB0aGFuIHRoYXQgdG8gYWNoaWV2ZSBhIHdpZ2dsaWVyIGN1cnZlci4gCgojIFNjYWxlcwojIyMgU2NhbGVzOiBDb250cm9sbGluZyBhZXN0aGV0aWMgbWFwcGluZwpBZXN0aGV0aWMgbWFwcGluZyAoaS5lLiwgd2l0aCBgYWVzKClgKSBvbmx5IHNheXMgdGhhdCBhIHZhcmlhYmxlIHNob3VsZCBiZSBtYXBwZWQgdG8gYW4gYWVzdGhldGljLiBJdCBkb2Vzbid0IHNheSBob3cgdGhhdCBzaG91bGQgaGFwcGVuLiBGb3IgZXhhbXBsZSwgd2hlbiBtYXBwaW5nIGEgdmFyaWFibGUgdG8gc2hhcGUgd2l0aCBgYWVzKHNoYXBlID0geClgIHlvdSBkb24ndCBzYXkgd2hhdCBzaGFwZXMgc2hvdWxkIGJlIHVzZWQuIFNpbWlsYXJseSwgYGFlcyhjb2xvciA9IHopYCBkb2Vzbid0IHNheSB3aGF0IGNvbG9ycyBzaG91bGQgYmUgdXNlZC4gRGVzY3JpYmluZyB3aGF0IGNvbG9ycy9zaGFwZXMvc2l6ZXMgZXRjLiB0byB1c2UgaXMgZG9uZSBieSBtb2RpZnlpbmcgdGhlIGNvcnJlc3BvbmRpbmcgX3NjYWxlXy4gSW4gYGdncGxvdDJgIHNjYWxlcyBpbmNsdWRlOgoKLSBQb3NpdGlvbgotIENvbG9yIGFuZCBmaWxsCi0gU2l6ZQotIFNoYXBlCi0gTGluZSB0eXBlCgpTY2FsZXMgYXJlIG1vZGlmaWVkIHdpdGggYSBzZXJpZXMgb2YgZnVuY3Rpb25zIHVzaW5nIGEgYHNjYWxlXzxhZXN0aGV0aWM+Xzx0eXBlPmAgbmFtaW5nIHNjaGVtZS4gVHJ5IHR5cGluZyBgc2NhbGVfPHRhYj5gIHRvIHNlZSBhIGxpc3Qgb2Ygc2NhbGUgbW9kaWZpY2F0aW9uIGZ1bmN0aW9ucy4KCiMjIyBDb21tb24gc2NhbGUgYXJndW1lbnRzCgoqIGBuYW1lYAogICAgKyB0aGUgZmlyc3QgYXJndW1lbnQgZ2l2ZXMgdGhlIGF4aXMgb3IgbGVnZW5kIHRpdGxlCgoqIGBsaW1pdHNgCiAgICArIHRoZSBtaW5pbXVtIGFuZCBtYXhpbXVtIG9mIHRoZSBzY2FsZSAKCiogYGJyZWFrc2AKICAgICsgdGhlIHBvaW50cyBhbG9uZyB0aGUgc2NhbGUgd2hlcmUgbGFiZWxzIHNob3VsZCBhcHBlYXIKICAgIAoqIGBsYWJlbHNgCiAgICArIHRoZSBsYWJlbHMgdGhhdCBhcHBlYXIgYXQgZWFjaCBicmVhawogICAgClNwZWNpZmljIHNjYWxlIGZ1bmN0aW9ucyBtYXkgaGF2ZSBhZGRpdGlvbmFsIGFyZ3VtZW50czsgZm9yIGV4YW1wbGUsIHRoZSBgc2NhbGVfY29sb3JfY29udGludW91c2AgZnVuY3Rpb24gaGFzIGFyZ3VtZW50cyBgbG93YCBhbmQgYGhpZ2hgIGZvciBzZXR0aW5nIHRoZSBjb2xvcnMgYXQgdGhlIGxvdyBhbmQgaGlnaCBlbmQgb2YgdGhlIHNjYWxlLgoKIyMjIFNjYWxlIG1vZGlmaWNhdGlvbiBleGFtcGxlcwpTdGFydCBieSBjb25zdHJ1Y3RpbmcgYSBkb3RwbG90IHNob3dpbmcgdGhlIGRpc3RyaWJ1dGlvbiBvZiBob21lIHZhbHVlcyBieSBEYXRlIGFuZCBTdGF0ZS4KCmBgYHtyfQpwMyA8LSBnZ3Bsb3QoaG91c2luZywgYWVzKHg9U3RhdGUsIHk9SG9tZS5QcmljZS5JbmRleCkpICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gInRvcCIsIGF4aXMudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplPTYpKQoKcDQgPC0gcDMgKyBnZW9tX3BvaW50KGFlcyhjb2xvcj1EYXRlKSwgYWxwaGE9MC41LCBzaXplPTEuNSwgcG9zaXRpb24gPSBwb3NpdGlvbl9qaXR0ZXIod2lkdGg9MC4yNSwgaGVpZ2h0PTApKQpwNApgYGAKCk5vdyBtb2RpZnkgdGhlIGJyZWFrcyBmb3IgdGhlIHggYXhpcyBhbmQgY29sb3Igc2NhbGVzCmBgYHtyfQpwNCArIHNjYWxlX3hfZGlzY3JldGUobmFtZT0iU3RhdGUgQWJicmV2aWF0aW9uIikgKyBzY2FsZV9jb2xvcl9jb250aW51b3VzKG5hbWU9IiIsIGJyZWFrcyA9IGMoMTk3NiwgMTk5NCwgMjAxMyksIGxhYmVscyA9IGMoIic3NiIsICInOTQiLCAiJzEzIikpCmBgYAoKTmV4dCBjaGFuZ2UgdGhlIGxvdyBhbmQgaGlnaCB2YWx1ZXMgdG8gYmx1ZSBhbmQgcmVkOgpgYGB7cn0KcDQgKyBzY2FsZV94X2Rpc2NyZXRlKCJTdGF0ZSBBYmJyZXZpYXRpb24iKSArIHNjYWxlX2NvbG9yX2NvbnRpbnVvdXMobmFtZSA9ICIiLCBicmVha3MgPSBjKDE5NzYsIDE5OTQsIDIwMTMpLCBsYWJlbHMgPSBjKCInNzYiLCAiJzk0IiwgIicxMyIpLCBsb3c9ImJsdWUiLCBoaWdoPSJyZWQiKQpgYGAKCmBgYHtyLCB3YXJuaW5nPUZ9CiMgV2UgbG9hZCBzY2FsZXMgcGFja2FnZSB0byBiZSBhYmxlIHRvIHVzZSB0aGUgbXV0ZWQoKSBmdW5jdGlvbgpsaWJyYXJ5KHNjYWxlcykKcDQgKwogIHNjYWxlX2NvbG9yX2NvbnRpbnVvdXMobmFtZT0iIiwKICAgICAgICAgICAgICAgICAgICAgICAgIGJyZWFrcyA9IGMoMTk3NiwgMTk5NCwgMjAxMyksCiAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBjKCInNzYiLCAiJzk0IiwgIicxMyIpLAogICAgICAgICAgICAgICAgICAgICAgICAgbG93ID0gbXV0ZWQoImJsdWUiKSwgaGlnaCA9IG11dGVkKCJyZWQiKSkKYGBgCgojIyMgVXNlIGRpZmZlcmVuY2UgY29sb3Igc2NhbGVzCmdncGxvdDIgaGFzIGEgd2lkZSB2YXJpZXR5IG9mIGNvbG9yIHNjYWxlczsgaGVyZSBpcyBhbiBleGFtcGxlIHVzaW5nIHNjYWxlX2NvbG9yX2dyYWRpZW50MiB0byBpbnRlcnBvbGF0ZSBiZXR3ZWVuIHRocmVlIGRpZmZlcmVudCBjb2xvcnMuCmBgYHtyfQpwNCArIHNjYWxlX2NvbG9yX2dyYWRpZW50MihuYW1lID0gIiIsIGJyZWFrcyA9IGMoMTk3NiwgMTk5NCwgMjAxMyksIGxhYmVscyA9IGMoIic3NiIsICInOTQiLCAiJzEzIiksIGxvdyA9IG11dGVkKCJibHVlIiksIGhpZ2ggPSBtdXRlZCgicmVkIiksIG1pZCA9ICJncmF5NjAiLCBtaWRwb2ludCA9IDE5OTQpCmBgYAoKIyMjIEV4ZXJjaXNlIDMKCjEuIENyZWF0ZSBhIHNjYXR0ZXIgcGxvdCB3aXRoIENQSSBvbiB0aGUgeCBheGlzIGFuZCBIREkgb24gdGhlIHkgYXhpcy4gQ29sb3IgdGhlIHBvaW50cyB0byBpbmRpY2F0ZSByZWdpb24uCjIuIE1vZGlmeSB0aGUgeCwgeSwgYW5kIGNvbG9yIHNjYWxlcyBzbyB0aGF0IHRoZXkgaGF2ZSBtb3JlIGVhc2lseS11bmRlcnN0b29kIG5hbWVzIChlLmcuLCBzcGVsbCBvdXQgIkh1bWFuIGRldmVsb3BtZW50IEluZGV4IiBpbnN0ZWFkIG9mICJIREkiKS4KMy4gTW9kaWZ5IHRoZSBjb2xvciBzY2FsZSB0byB1c2Ugc3BlY2lmaWMgdmFsdWVzIG9mIHlvdXIgY2hvb3NpbmcuIEhpbnQ6IHNlZSBgP3NjYWxlX2NvbG9yX21hbnVhbGAuCgpgYGB7cn0KIyBDcmVhdGUgYSBzY2F0dGVycGxvdCB3aXRoIENQSSBvbiB0aGUgeCBheGlzIGFuZCBIREkgb24gdGhlIHkgYXhpcy4gQ29sb3IgdGhlIHBvaW50cyB0byBpbmRpY2F0ZSByZWdpb24KcGxvdDMgPC0gZ2dwbG90KGRhdGEgPSBkYXQsIGFlcyh4PUNQSSwgeT1IREkpKSArIGdlb21fcG9pbnQoYWVzKGNvbG9yPVJlZ2lvbikpCnBsb3QzCmBgYAoKYGBge3J9CiMgTW9kaWZ5IHRoZSB4LCB5LCBhbmQgY29sb3Igc2NhbGVzIHNvIHRoYXQgdGhleSBoYXZlIG1vcmUgZWFzaWx5LXVuZGVyc3Rvb2QgbmFtZXMgKGUuZy4sIHNwZWxsIG91dCAiSHVtYW4gZGV2ZWxvcG1lbnQgSW5kZXgiIGluc3RlYWQgb2YgIkhESSIpLgoKcGxvdDMgKyBzY2FsZV94X2NvbnRpbnVvdXMobmFtZT0iQ29ycnVwdGlvbiBQZXJjZXB0aW9uIEluZGV4IikgKyBzY2FsZV95X2NvbnRpbnVvdXMobmFtZT0iSHVtYW4gRGV2ZWxvcG1lbnQgSW5kZXgiKSArIHNjYWxlX2NvbG9yX2Rpc2NyZXRlKGxhYmVscz1jKCJBbWVyaWNhcyIsICJBc2lhIFBhY2lmaWMiLCAiRWFzdGVybiBFdXJvcGUgYW5kIENlbnRyYWwgQXNpYSIsICJXZXN0ZXJuIEV1cm9wZSIsICJNaWRkbGUgRWFzdCBhbmQgTm9ydGggQWZyaWNhIiwgIlN1Yi1TYWhhcmFuIEFmcmljYSIpKQpgYGAKCmBgYHtyfQojIE1vZGlmeSB0aGUgY29sb3Igc2NhbGUgdG8gdXNlIHNwZWNpZmljIHZhbHVlcyBvZiB5b3VyIGNob29zaW5nLiBIaW50OiBzZWUgYD9zY2FsZV9jb2xvcl9tYW51YWxgCnBsb3QzICsgc2NhbGVfeF9jb250aW51b3VzKG5hbWU9IkNvcnJ1cHRpb24gUGVyY2VwdGlvbiBJbmRleCIpICsgc2NhbGVfeV9jb250aW51b3VzKG5hbWU9Ikh1bWFuIERldmVsb3BtZW50IEluZGV4IikgKyBzY2FsZV9jb2xvcl9tYW51YWwobmFtZSA9ICJSZWdpb24iLCB2YWx1ZXMgPSBjKCJkb2RnZXJibHVlNCIsICJkYXJrb2xpdmVncmVlbjQiLCAiZGFya29yY2hpZDMiLCAiZ29sZGVucm9kMSIsICJjaG9jb2xhdGUyIiwgInZpb2xldHJlZDQiKSwgbGFiZWxzPWMoIkFtZXJpY2FzIiwgIkFzaWEgUGFjaWZpYyIsICJFYXN0ZXJuIEV1cm9wZSBhbmQgQ2VudHJhbCBBc2lhIiwgIldlc3Rlcm4gRXVyb3BlIiwgIk1pZGRsZSBFYXN0IGFuZCBOb3J0aCBBZnJpY2EiLCAiU3ViLVNhaGFyYW4gQWZyaWNhIikpIApgYGAKCmBgYHtyfQojIEJvbnVzOiBBZGQgZ2VvbV90ZXh0CnBsb3QzICsgc2NhbGVfeF9jb250aW51b3VzKG5hbWU9IkNvcnJ1cHRpb24gUGVyY2VwdGlvbiBJbmRleCIpICsgc2NhbGVfeV9jb250aW51b3VzKG5hbWU9Ikh1bWFuIERldmVsb3BtZW50IEluZGV4IikgKyBzY2FsZV9jb2xvcl9tYW51YWwobmFtZSA9ICJSZWdpb24iLCB2YWx1ZXMgPSBjKCJkb2RnZXJibHVlNCIsICJkYXJrb2xpdmVncmVlbjQiLCAiZGFya29yY2hpZDMiLCAiZ29sZGVucm9kMSIsICJjaG9jb2xhdGUyIiwgInZpb2xldHJlZDQiKSwgbGFiZWxzPWMoIkFtZXJpY2FzIiwgIkFzaWEgUGFjaWZpYyIsICJFYXN0ZXJuIEV1cm9wZSBhbmQgQ2VudHJhbCBBc2lhIiwgIldlc3Rlcm4gRXVyb3BlIiwgIk1pZGRsZSBFYXN0IGFuZCBOb3J0aCBBZnJpY2EiLCAiU3ViLVNhaGFyYW4gQWZyaWNhIikpICsgZ2VvbV90ZXh0X3JlcGVsKGFlcyhsYWJlbD1Db3VudHJ5KSwgZGF0YSA9IGRhdFtkYXQkQ291bnRyeSAlaW4lIGMoIlNpbmdhcG9yZSIsICJNYWxheXNpYSIsICJJbmRvbmVzaWEiLCAiVmlldG5hbSIsIlBoaWxpcHBpbmVzIiwgIlRoYWlsYW5kIiksXSwgc2l6ZT0yKQpgYGAKCiMgRmFjZXRpbmcKIyMjIEZhY2V0aW5nIGNvbmNlcHRzCkZhY2V0aW5nIGlzIGBnZ3Bsb3QyYCBwYXJsYW5jZSBmb3Igc21hbGwgbXVsdGlwbGVzLiBUaGUgaWRlYSBpcyB0byBjcmVhdGUgc2VwYXJhdGUgZ3JhcGhzIGZvciBzdWJzZXRzIG9mIGRhdGEuIGBnZ3Bsb3QyYCBvZmZlcnMgdHdvIGZ1bmN0aW9ucyBmb3IgY3JlYXRpbmcgc21hbGwgbXVsdGlwbGVzOgoKLSBmYWNldF93cmFwKCk6IGRlZmluZSBzdWJzZXRzIGFzIHRoZSBsZXZlbHMgb2YgYSBzaW5nbGUgZ3JvdXBpbmcgdmFyaWFibGUKLSBmYWNldF9ncmlkKCk6IGRlZmluZSBzdWJzZXRzIGFzIHRoZSBjcm9zc2luZyBvZiB0d28gZ3JvdXBpbmcgdmFyaWFibHMKClRoaXMgZmFjaWxpdGF0ZXMgY29tcGFyaXNvbiBhbW9uZyBwbG90cywgbm90IGp1c3Qgb2YgZ2VvbXMgd2l0aGluIGEgcGxvdC4KCiMjIyBXaGF0IGlzIHRoZSB0cmVuZCBpbiBob3VzaW5nIHByaWNlcyBpbiBlYWNoIHN0YXRlPwpgYGB7cn0KIyBTdGFydCBieSB1c2luZyBhIHRlY2huaXF1ZSB3ZSBhbHJlYWR5IGtub3c6IG1hcCBTdGF0ZSB0byBjb2xvcgpwNSA8LSBnZ3Bsb3QoaG91c2luZywgYWVzKHggPSBEYXRlLCB5ID0gSG9tZS5WYWx1ZSkpCnA1ICsgZ2VvbV9saW5lKGFlcyhjb2xvciA9IFN0YXRlKSkKYGBgCgpUaGVyZSBhcmUgdHdvIHByb2JsZW1zIGhlcmXigJN0aGVyZSBhcmUgdG9vIG1hbnkgc3RhdGVzIHRvIGRpc3Rpbmd1aXNoIGVhY2ggb25lIGJ5IGNvbG9yLCBhbmQgdGhlIGxpbmVzIG9ic2N1cmUgb25lIGFub3RoZXIuCgojIyMgRmFjZXRpbmcgdG8gdGhlIHJlc2N1ZQpXZSBjYW4gcmVtZWR5IHRoZSBkZWZpY2llbmNpZXMgb2YgdGhlIHByZXZpb3VzIHBsb3QgYnkgZmFjZXRpbmcgYnkgc3RhdGUgcmF0aGVyIHRoYW4gbWFwcGluZyBzdGF0ZSB0byBjb2xvci4KYGBge3J9CnA1IDwtIHA1ICsgZ2VvbV9saW5lKCkgKyBmYWNldF93cmFwKH5TdGF0ZSwgbmNvbD0xMCkKcDUKYGBgCgpUaGVyZSBpcyBhbHNvIGEgZmFjZXRfZ3JpZCgpIGZ1bmN0aW9uIGZvciBmYWNldGluZyBpbiB0d28gZGltZW5zaW9ucy4KCiMgVGhlbWVzClRoZSBnZ3Bsb3QyIHRoZW1lIHN5c3RlbSBoYW5kbGVzIG5vbi1kYXRhIHBsb3QgZWxlbWVudHMgc3VjaCBhczoKCi0gQXhpcyBsYWJlbHMKLSBQbG90IGJhY2tncm91bmQKLSBGYWNldCBsYWJlbCBiYWNrcm91bmQKLSBMZWdlbmQgYXBwZWFyYW5jZQoKQnVpbHQtaW4gdGhlbWVzIGluY2x1ZGU6CgotIHRoZW1lX2dyYXkoKSAoZGVmYXVsdCkKLSB0aGVtZV9idygpCi0gdGhlbWVfY2xhc3NjKCkKCmBgYHtyfQpwNSArIHRoZW1lX2xpbmVkcmF3KCkKYGBgCgpgYGB7cn0KcDUgKyB0aGVtZV9saWdodCgpCmBgYAoKIyMjIE92ZXJyaWRpbmcgdGhlbWUgZGVmYXVsdHMKU3BlY2lmaWMgdGhlbWUgZWxlbWVudHMgY2FuIGJlIG92ZXJyaWRkZW4gdXNpbmcgYHRoZW1lKClgOgpgYGB7cn0KcDUgKyB0aGVtZV9taW5pbWFsKCkgKyB0aGVtZSh0ZXh0ID0gZWxlbWVudF90ZXh0KGNvbG9yPSJwdXJwbGUiKSkKYGBgCgpBbGwgdGhlbWUgb3B0aW9ucyBhcmUgZG9jdW1lbnRlZCBpbiBgP3RoZW1lYC4KIAojIyMgQ3JlYXRpbmcgYW5kIHNhdmluZyBuZXcgdGhlbWVzCllvdSBjYW4gY3JlYXRlIG5ldyB0aGVtZXMsIGFzIGluIHRoZSBmb2xsb3dpbmcgZXhhbXBsZToKYGBge3J9CnRoZW1lX25ldyA8LSB0aGVtZV9idygpICsKICB0aGVtZShwbG90LmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3Qoc2l6ZT0xLCBjb2xvcj0ibWFyb29uIiwgZmlsbD0iYmxhY2siKSwgCiAgICAgICAgdGV4dD1lbGVtZW50X3RleHQoc2l6ZT0xMSwgY29sb3I9ImdvbGRlbnJvZDIiKSwKICAgICAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChjb2xvcj0ib3JhbmdlIiksCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoY29sb3I9Im9yYW5nZSIpLAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICJnb2xkZW5yb2QzIiksIAogICAgICAgIHN0cmlwLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbD0ibWFyb29uIikpCgpwNSArIHRoZW1lX25ldwpgYGAKCiMgQ2hhbGxlbmdlOiBSZWNyZWF0ZSBhbiBFY29ub21pc3QgZ3JhcGgKQnVpbGRpbmcgb2ZmIG9mIHRoZSBncmFwaGljcyB5b3UgY3JlYXRlZCBpbiB0aGUgcHJldmlvdXMgZXhlcmNpc2VzLCBwdXQgdGhlIGZpbmlzaGluZyB0b3VjaGVzIHRvIG1ha2UgaXQgYXMgY2xvc2UgYXMgcG9zc2libGUgdG8gdGhlIFtvcmlnaW5hbCBlY29ub21pc3QgZ3JhcGhdKGh0dHA6Ly93d3cuZWNvbm9taXN0LmNvbS9ub2RlLzIxNTQxMTc4KQoKYGBge3J9CiMgSW1wb3J0IHRoZSBmb250cyBpbnN0YWxsZWQgb24gb3VyIHN5c3RlbS4gT25seSBkbyB0aGlzIG9uY2UuIAojIGxpYnJhcnkoZXh0cmFmb250KQojIGZvbnRfaW1wb3J0KCkKCiMgUmVnaXN0ZXIgdGhlIGZvbnQgd2l0aCB0aGUgUERGIG91dHB1dCBkZXZpY2UKIyBsb2FkZm9udHMoKQoKIyBSZW5hbWUgLyBSZS1vcmRlciB0aGUgZmFjdG9yIGxldmVscyBiZWZvcmUgdGhlIHBsb3QKZGF0JFJlZ2lvbiA8LSBmYWN0b3IoZGF0JFJlZ2lvbiwgbGV2ZWxzID0gYygiRVUgVy4gRXVyb3BlIiwgIkFtZXJpY2FzIiwgIkFzaWEgUGFjaWZpYyIsICJFYXN0IEVVIENlbXQgQXNpYSIsICJNRU5BIiwgIlNTQSIpKQoKIyBMaXN0IG9mIGNvdW50cmllcyB3aXRoIGxhYmVscwpjb3VudHJ5TGlzdCA8LSBjKCJOZXcgWmVhbGFuZCIsICJTaW5nYXBvcmUiLCAiTm9yd2F5IiwgIkphcGFuIiwiR2VybWFueSIsICJCcml0YWluIiwgIkJhcmJhZG9zIiwgIlVTIiwgIkZyYW5jZSIsICJTcGFpbiIsICJCb3Rzd2FuYSIsICJDYXBlIFZlcmRlIiwgIkJodXRhbiIsICJJdGFseSIsICJHcmVlY2UiLCAiQnJhemlsIiwgIkFyZ2VudGluYSIsICJDaGluYSIsICJTb3V0aCBBZnJpY2EiLCAiUndhbmRhIiwgIkluZGlhIiwgIkNvbmdvIiwgIlJ1c3NpYSIsICJWZW5lenVlbGEiLCAiSXJhcSIsICJNeWFubWFyIiwgIlN1ZGFuIiwgIkFmZ2hhbmlzdGFuIikKCiMgRGV2ZWxvcCBvdXIgbmV3IHRoZW1lCmVjb25fdGhlbWUgPC0gdGhlbWVfYncoKSArIAogIHRoZW1lKGFzcGVjdC5yYXRpbyA9IDMvNywKICAgICAgICBsZWdlbmQudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gNywgZmFtaWx5PSJBcmlhbCBOYXJyb3ciKSwKICAgICAgICBsZWdlbmQucG9zaXRpb24gPSBjKDAsMSksCiAgICAgICAgbGVnZW5kLmp1c3RpZmljYXRpb24gPSAibGVmdCIsCiAgICAgICAgbGVnZW5kLmRpcmVjdGlvbiA9ICJob3Jpem9udGFsIiwKICAgICAgICBsZWdlbmQuc3BhY2luZyA9IHVuaXQoMiwgImxpbmVzIiksCiAgICAgICAgcGxvdC5tYXJnaW4gPSB1bml0KGMoMC41LCAxLCAwLjUsIDAuNSksIHVuaXRzPSJsaW5lIiksCiAgICAgICAgYXhpcy50aXRsZS55PWVsZW1lbnRfdGV4dChmYWNlPSJpdGFsaWMiKSwKICAgICAgICBheGlzLnRpdGxlLng9ZWxlbWVudF90ZXh0KGZhY2U9Iml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiwgc2l6ZT0xMyksCiAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmdyaWQubWFqb3IueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ncmlkLm1pbm9yLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9saW5lKHNpemU9LjEsIGNvbG9yPSJncmV5NjYiKSwKICAgICAgICBwYW5lbC5ncmlkLm1pbm9yLnkgPSBlbGVtZW50X2JsYW5rKCkpCgplY29uIDwtIGdncGxvdChkYXRhID0gZGF0LCBhZXMoeD1DUEksIHk9SERJKSkgKyAKICBnZW9tX3BvaW50KGFlcyhjb2xvcj1SZWdpb24pLCBzaXplPTEuNSwgZmlsbD00LCBzaGFwZT0xLCBzdHJva2UgPSAxLjIpICsgCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwgZm9ybXVsYSA9IHkgfiBsb2coeCksIHNlPUZBTFNFLCBjb2xvcj0icmVkIiwgbGluZXR5cGU9MSwgd2VpZ2h0PTIpICsgCiAgZ2VvbV90ZXh0X3JlcGVsKGFlcyhsYWJlbD1Db3VudHJ5KSwgZGF0YSA9IGRhdFtkYXQkQ291bnRyeSAlaW4lIGNvdW50cnlMaXN0LF0sIHNpemU9Mi4yKSArCiAgc2NhbGVfeF9jb250aW51b3VzKG5hbWU9IkNvcnJ1cHRpb24gUGVyY2VwdGlvbnMgSW5kZXgsIDIwMTEoMTA9bGVhc3QgY29ycnVwdCkiLCBicmVha3M9c2VxKDEsMTAsYnk9MSksIGxpbWl0cz1jKDEsMTApKSArCiAgc2NhbGVfeV9jb250aW51b3VzKG5hbWU9Ikh1bWFuIERldmVsb3BtZW50IEluZGV4LCAyMDExKDE9YmVzdCkiLCBicmVha3M9c2VxKDAsIDEuMCwgYnk9MC4xKSwgbGltaXRzPSBjKDAuMiwxLjApKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKG5hbWUgPSAiIiwgdmFsdWVzID0gYygiIzJkNjA3NCIsICIjMjlhZGRlIiwgIiNiYmU5ZmIiLCAiIzE4OGM4MSIsICIjZjI1MjNhIiwgIiM3YzI1MTAiKSwgbGFiZWxzPWMoIkVVIFcuIEV1cm9wZSI9Ik9FQ0QiLCAiQW1lcmljYXMiID0gIkFtZXJpY2FzIiwgIkFzaWEgUGFjaWZpYyI9IkFzaWEgJiBPY2VhbmlhIiwgIkVhc3QgRVUgQ2VtdCBBc2lhIj0iQ2VudHJhbCAmIEVhc3Rlcm4gRXVyb3BlIiwgIk1FTkEiPSJNaWRkbGUgRWFzdCAmIG5vcnRoIEFmcmljYSIsICJTU0EiPSJTdWItU2FoYXJhbiBBZnJpY2EiKSkgKwogIGxhYnModGl0bGU9IkNvcnJ1cHRpb24gYW5kIGh1bWFuIGRldmVsb3BtZW50IikgKwogIGd1aWRlcyhjb2xvcj1ndWlkZV9sZWdlbmQobnJvdz0xKSkgKwogIGVjb25fdGhlbWUgCmVjb24KYGBgCgoKCgoK